package com.atguigu.auth.service.impl;

import com.atguigu.auth.mapper.SysMenuMapper;
import com.atguigu.auth.service.SysMenuService;
import com.atguigu.auth.service.SysRoleMenuService;
import com.atguigu.auth.utils.MenuHelper;
import com.atguigu.common.config.exception.GuiguException;
import com.atguigu.model.system.SysMenu;
import com.atguigu.model.system.SysRoleMenu;
import com.atguigu.vo.system.AssignMenuVo;
import com.atguigu.vo.system.MetaVo;
import com.atguigu.vo.system.RouterVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 1 * <p>
 * 菜单表 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2023-04-15
 */
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    @Autowired
    private SysRoleMenuService sysRoleMenuService;

    /**
     * 获取菜单列表
     *
     * @return {@link List <SysMenu>}
     */
    @Override
    public List<SysMenu> findNodes() {
        // 1.查询所有菜单数据
        List<SysMenu> sysMenuList = list();

        // 2.构建树形结构
        // {
        //     第一层
        //     children:[
        //         {
        //             第二层
        //                     ...
        //         }
        //     ]
        // }

        return MenuHelper.buildTree(sysMenuList);
    }

    /**
     * 删除菜单
     *
     * @param id {@link Long}
     */
    @Override
    public void removeMenuById(Long id) {
        // 判断当前菜单是否有下一层级
        LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysMenu::getParentId, id);
        int count = count(wrapper);
        if (count > 0) {
            throw new GuiguException(201, "菜单不能删除");
        }
        removeById(id);
    }

    /**
     * 查询所有菜单和角色分配的菜单
     *
     * @param roleId {@link Long}
     * @return {@link List<SysMenu>}
     */
    @Override
    public List<SysMenu> findMenuByRoleId(Long roleId) {
        // 1.查询所有菜单-添加条件 status = 1
        LambdaQueryWrapper<SysMenu> wrapperSysMenu = new LambdaQueryWrapper<>();
        wrapperSysMenu.eq(SysMenu::getStatus, 1);
        List<SysMenu> allSysMenuList = list(wrapperSysMenu);

        // 2.根据角色 id roleId 查询 角色菜单关系表里面 角色 id 对应所有的菜单 id
        LambdaQueryWrapper<SysRoleMenu> wrapperSysRoleMenu = new LambdaQueryWrapper<>();
        wrapperSysRoleMenu.eq(SysRoleMenu::getRoleId, roleId);
        List<SysRoleMenu> sysRoleMenuList = sysRoleMenuService.list(wrapperSysRoleMenu);

        // 3.根据获取菜单 id 获取对应菜单对象
        List<Long> menuIdList = sysRoleMenuList.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());

        // 3.1拿着菜单id和所有菜单集合里面id进行比较，如果相同封装
        allSysMenuList.forEach(item -> {
            if (menuIdList.contains(item.getId())) {
                item.setSelect(true);
                return;
            }
            item.setSelect(false);
        });

        // 4.返回规定格式菜单列表
        return MenuHelper.buildTree(allSysMenuList);
    }

    /**
     * 为角色分配菜单
     *
     * @param assignMenuVo {@link AssignMenuVo}
     */
    @Override
    public void doAssign(AssignMenuVo assignMenuVo) {
        // 1.根据角色 id 删除菜单角色表 分配数据
        LambdaQueryWrapper<SysRoleMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysRoleMenu::getRoleId, assignMenuVo.getRoleId());
        sysRoleMenuService.remove(wrapper);

        // 2.从参数里面获取角色重新分配菜单 id 列表，进行遍历，把每个 id 数据添加菜单角色表
        List<Long> menuIdList = assignMenuVo.getMenuIdList();
        List<SysRoleMenu> sysRoleMenuList = menuIdList.stream().map(item -> {
            if (StringUtils.isEmpty(item)) {
                return null;
            }
            SysRoleMenu sysRoleMenu = new SysRoleMenu();
            sysRoleMenu.setMenuId(item);
            sysRoleMenu.setRoleId(assignMenuVo.getRoleId());
            return sysRoleMenu;
        }).filter(item -> !StringUtils.isEmpty(item)).collect(Collectors.toList());
        sysRoleMenuService.saveBatch(sysRoleMenuList);
    }

    /**
     * 根据用户id查询用户可以操作的菜单列表(路由列表)
     *
     * @param userId {@link Long} 用户 id
     * @return {@link List<RouterVo>}
     */
    @Override
    public List<RouterVo> findUserMenuListByUserId(Long userId) {
        List<SysMenu> sysMenuList;
        // 1.判断当前用户是否是管理员   userId = 1 是管理员
        // 1.1 如果是管理员,查询所有菜单列表
        if (userId == 1) {
            // 查询所有菜单列表
            LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SysMenu::getStatus, 1);
            wrapper.orderByAsc(SysMenu::getSortValue);
            sysMenuList = list(wrapper);
        } else {
            // 1.2 如果不是管理员,根据 userId 查询可以操作菜单列表
            // 多表关联查询:用户角色关系表 角色菜单关系表 菜单表
            sysMenuList = baseMapper.findMenuListByUserId(userId);
        }

        // 2.把查询出来的数据列表-构建成框架要求的路由结构
        // sysMenuTreeList 是层级菜单
        List<SysMenu> sysMenuTreeList = MenuHelper.buildTree(sysMenuList);
        // 构建成框架要求的路由结构
        return this.buildRouter(sysMenuTreeList);
    }

    /**
     * 构建成框架要求的路由结构
     * hidden: true                   if set true, item will not show in the sidebar(default is false)
     * true：在左侧菜单栏不显示 false: 在左侧菜单栏显示
     * alwaysShow: true               if set true, will always show the root menu
     * if not set alwaysShow, when item has more than one children route,
     * it will become nested mode, otherwise not show the root menu
     * 如果设置为 true，将始终显示根菜单 如果未设置 alwaysShow，当项目有多个子路由时，它将变为嵌套模式，否则不显示根菜单
     *
     * @param menus {@link List<SysMenu>} 层级菜单
     * @return {@link List<RouterVo>}
     */
    private List<RouterVo> buildRouter(List<SysMenu> menus) {
        List<RouterVo> routers = new ArrayList<>();
        // menus 遍历
        List<RouterVo> currentRouterList = menus.stream().map(item -> {
            RouterVo router = new RouterVo();
            router.setHidden(false);
            router.setAlwaysShow(false);
            router.setPath(getRouterPath(item));
            router.setComponent(item.getComponent());
            router.setMeta(new MetaVo(item.getName(), item.getIcon()));
            List<SysMenu> children = item.getChildren();
            // 如果当前是菜单 type=1，需将按钮对应的路由加载出来，如：“角色授权”按钮对应的路由在“系统管理”下面
            if (item.getType() == 1) {
                // 加载出来隐藏路由 type=1 下一级是 按钮(按钮有组件->说明是隐藏路由)
                List<RouterVo> childrenRouter = children.stream().filter(hiddenMenu -> !StringUtils.isEmpty(hiddenMenu.getComponent())).map(hiddenMenu -> {
                    RouterVo hiddenRouter = new RouterVo();
                    hiddenRouter.setHidden(true);
                    hiddenRouter.setAlwaysShow(false);
                    hiddenRouter.setPath(getRouterPath(hiddenMenu));
                    hiddenRouter.setComponent(hiddenMenu.getComponent());
                    hiddenRouter.setMeta(new MetaVo(hiddenMenu.getName(), hiddenMenu.getIcon()));
                    return hiddenRouter;
                }).collect(Collectors.toList());
                routers.addAll(childrenRouter);
            } else if (!CollectionUtils.isEmpty(children)) {
                // type != 1(只有 type = 0 根级目录，type = 2的时候是按钮，按钮没有 children) 且包含子菜单，显示为根级目录
                router.setAlwaysShow(true);
                // 递归处理下一级路由
                router.setChildren(buildRouter(children));
            }
            return router;
        }).collect(Collectors.toList());
        routers.addAll(currentRouterList);
        return routers;
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    private String getRouterPath(SysMenu menu) {
        String routerPath = "/" + menu.getPath();
        if (menu.getParentId().intValue() != 0) {
            routerPath = menu.getPath();
        }
        return routerPath;
    }

    /**
     * 根据用户id获取用户可以操作按钮列表
     *
     * @param userId {@link Long}
     * @return {@link List<String>}
     */
    @Override
    public List<String> findUserPermsByUserId(Long userId) {
        // 1 判断是否是管理员,如果是管理员,查询所有按钮列表
        List<SysMenu> sysMenuList;
        if (userId == 1) {
            LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SysMenu::getStatus, 1);
            sysMenuList = list(wrapper);
        } else {
            // 2.如果不是管理员,根据 userId 查询可以操作按钮列表
            // 多表关联查询:用户角色关系表 角色菜单关系表 菜单表
            sysMenuList = baseMapper.findMenuListByUserId(userId);
        }

        // 从查询出来的数据里面,获取可以操作按钮值的list 集合,返回
        return sysMenuList.stream().filter(item -> item.getType() == 2)
                .map(SysMenu::getPerms)
                .collect(Collectors.toList());
    }
}
